这一篇整理一下DispatcherTimer. 看DispatcherTimer之前, 先看一下.Net中的其他Timer
Timer(C# in a nutshell 6th)
The .NET Framework provides four timers. Two of these are general-purpose multithreaded timers:
- System.Threading.Timer
- System.Timers.Timer
The other two are special-purpose single-threaded timers:
- System.Windows.Forms.Timer (Windows Forms timer)
- System.Windows.Threading.DispatcherTimer (WPF timer)
The multithreaded timers are more powerful, accurate, and flexible; the singlethreaded timers are safer and more convenient for running simple tasks that update Windows Forms controls or WPF elements.
System.Threading.Timer is the simplest multithreaded timer: it has just a constructor and two methods
The .NET Framework provides another timer class of the same name in the System.Timers namespace. This simply wraps the System.Threading.Timer (参考:https://www.gnu.org/software/dotgnu/pnetlib-doc/System/Threading/Timer.html)
Multithreaded timers use the thread pool to allow a few threads to serve many timers. This means that the callback method or Elapsed event may fire on a different thread each time it is called. Furthermore, the Elapsed event always fires(approximately) on time—regardless of whether the previous Elapsed event finished executing. Hence, callbacks or event handlers must be thread-safe.
使用的是线程池, 回调函数有可能是在不同的线程中被调用. 不管前一个调用是否完成, 后一个调用会如期而至, 因此回调函数必须是线程安全的(可重入的)
定时器精度在10-20ms之间
而DispatcherTimer的时间精度就没那么准(参考:https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer.interval(v=vs.110).aspx)
Timers are not guaranteed to execute exactly when the time interval occurs, but they are guaranteed to not execute before the time interval occurs. This is because DispatcherTimer operations are placed on the Dispatcher queue like other operations. When the DispatcherTimer operation executes is dependent on the other jobs in the queue and their priorities.
如果要计时的话, 用stopwatch, 参考https://stackoverflow.com/questions/4251644/getting-elapsed-time-with-dispatchtimer-to-1-millisecond-accuracy
和https://social.msdn.microsoft.com/forums/windowsapps/en-us/058a755e-059b-45ee-b5ec-3d35f3b53515/dispatchertimer-accuracy
DispatcherTimer这个类不复杂, 阅读它的代码能学到不少东西, 可以进一步了解Dispatcher的运行方式.
1 | using System; |
从DispatcherTimer的使用方式入手1
2
3
4
5// DispatcherTimer setup
dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0,0,1);
dispatcherTimer.Start();
无参构造方法DispatcherTimer()将DispatcherPriority设为Background1
2
3
4
5
6
7/// <summary>
/// Creates a timer that uses the current thread's Dispatcher to
/// process the timer event at background priority.
/// </summary>
public DispatcherTimer() : this(DispatcherPriority.Background) // NOTE: should be Priority Dispatcher.BackgroundPriority
{
}
Background优先级很低, 所以不一定能够按时执行.
再看Start方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/// <summary>
/// Starts the timer.
/// </summary>
public void Start()
{
lock(_instanceLock)
{
if(!_isEnabled)
{
_isEnabled = true;
Restart();
}
}
}
调用的是Restart()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31private void Restart()
{
lock(_instanceLock)
{
if (_operation != null)
{
// Timer has already been restarted, e.g. Start was called form the Tick handler.
return;
}
// BeginInvoke a new operation.
_operation = _dispatcher.BeginInvoke(
DispatcherPriority.Inactive,
new DispatcherOperationCallback(FireTick),
null);
_dueTimeInTicks = Environment.TickCount + (int) _interval.TotalMilliseconds;
if (_interval.TotalMilliseconds == 0 && _dispatcher.CheckAccess())
{
// shortcut - just promote the item now
Promote();
}
else
{
_dispatcher.AddTimer(this);
}
}
}
这个方法比较关键
- 它往Dispatcher Queue里面放了一个Operation, 但它的优先级是DispatcherPriority.Inactive. 顾名思义, 这个优先级表示该Operation是不激活状态. 它不会被执行
1
2
3
4/// <summary>
/// Operations at this priority are not processed.
/// </summary>
Inactive = 0,
因为时间间隔不到, Operation不能被执行. 只有时间间隔到了, Operation才会被激活. 如何激活? 且看下面
- Promote()方法, 就是激活该Operation. Promote是提升的意思, 也就是提升Operation的优先级.
1
2
3
4
5
6
7
8
9
10
11internal void Promote() // called from Dispatcher
{
lock(_instanceLock)
{
// Simply promote the operation to it's desired priority.
if(_operation != null)
{
_operation.Priority = _priority;
}
}
}
_priority构造的时候为DispatcherPriority.Background. 即从DispatcherPriority.Inactive提升(Promote)到
DispatcherPriority.Background. 如此, Operation就可以被执行了.
注意called from Dispatcher, 表示, 时间间隔到了, 提升Operation的优先级是由Dispatcher来完成的.
Dispatcher和DispatcherTimer有什么关系呢? 且看下面
- _dispatcher.AddTimer(this);
1
2
3
4
5
6
7
8
9
10
11
12internal void AddTimer(DispatcherTimer timer)
{
lock(_instanceLock)
{
if(!_hasShutdownFinished) // Could be a non-dispatcher thread, lock to read
{
_timers.Add(timer);
_timersVersion++;
}
}
UpdateWin32Timer();
}
添加了一个DispatcherTimer, 需要UpdateWin32Timer, 去操作系统设置定时器
1 | internal void UpdateWin32Timer() // Called from DispatcherTimer |
这样, 系统才会发WM_TIMER给窗口处理函数, Dispatcher才会从DispatcherQueue里面取出Operation, 看下面的代码1
2
3
4
5
6
7
8
9
10private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
else if(message == WindowMessage.WM_TIMER && (int) wParam == TIMERID_TIMERS)
{
// We want 1-shot only timers. So stop the timer
// that just fired.
KillWin32Timer();
PromoteTimers(Environment.TickCount);
}
PromoteTimers()判断时间间隔有没有到; 如果到了,调用的是timer.Promote()方法, 提升Operation的优先级1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24internal void PromoteTimers(int currentTimeInTicks)
while(iTimer < _timers.Count)
{
// WARNING: this is vulnerable to wrapping
if(timers[iTimer]._dueTimeInTicks - currentTimeInTicks <= 0) //判断时间有没有到
{
// Remove this timer from our list.
// Do not increment the index.
timer = timers[iTimer];
timers.RemoveAt(iTimer);
break;
}
else
{
iTimer++;
}
}
// Now that we are outside of the lock, promote the timer.
if(timer != null)
{
timer.Promote();
}
- 时间间隔到了, 执行FireTick方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private object FireTick(object unused)
{
// The operation has been invoked, so forget about it.
_operation = null;
// The dispatcher thread is calling us because item's priority
// was changed from inactive to something else.
if(Tick != null)
{
Tick(this, EventArgs.Empty);
}
// If we are still enabled, start the timer again.
if(_isEnabled)
{
Restart();//再把op放到queue中
}
return null;
}
前面由于DispatcherOperation已经从DispatcherQueue里面取出, 所以这里需要重新再向DispatcherQueue放入DispatcherOperation, 等下一个时间间隔来临时执行. 做法是, 再次调用Restart方法.